GLOBAL.setmetatable(env, {
    __index = function(_, k)
        return GLOBAL.rawget(GLOBAL, k)
    end
})

TUNING.item_port_finiteuses = 0
TUNING.item_port_sanity = 30
TUNING.item_port_power = 60
TUNING.item_port_hunger = 75

local function playercontroller(self)
    local old_GetMapActions = self.GetMapActions
    function self:GetMapActions(position)
        local LMBaction, RMBaction = old_GetMapActions(self, position)
        local inventory = self.inst.components.inventory or self.inst.replica.inventory
        if inventory and inventory:EquipHasTag("obrsword") then
            local act = BufferedAction(self.inst, nil, ACTIONS.ITEM_PORTAL)
            RMBaction = self:RemapMapAction(act, position)
            return LMBaction, RMBaction
        end
        return LMBaction, RMBaction
    end
    local old_OnMapAction = self.OnMapAction
    function self:OnMapAction(actioncode, position, maptarget, mod_name)
        old_OnMapAction(self, actioncode, position, maptarget, mod_name)
        local act = MOD_ACTIONS_BY_ACTION_CODE[STRINGS.ITEM_MODNAME][actioncode]
        if act == nil or not act.map_action then
            return
        end
        if self.ismastersim then
            local LMBaction, RMBaction = self:GetMapActions(position, maptarget, actiondef)
            if act.rmb then
                if RMBaction then
                    self.locomotor:PushAction(RMBaction, true)
                end
            else
                if LMBaction then
                    self.locomotor:PushAction(LMBaction, true)
                end
            end
        elseif self.locomotor == nil and not self.inst.item_portal then
            self.inst.item_portal = true
            if self.inst.item_task_portal == nil then
                self.inst.item_task_portal = self.inst:DoTaskInTime(9, function()
                    self.inst.item_portal = false
                    self.inst.item_task_portal = nil
                end)
            end
            SendRPCToServer(RPC.DoActionOnMap, actioncode, position.x, position.z)
        elseif self:CanLocomote() then
            local _, RMBaction = self:GetMapActions(position, maptarget, actiondef)
            RMBaction.preview_cb = function()
                SendRPCToServer(RPC.DoActionOnMap, actioncode, position.x, position.z)
            end
            self.locomotor:PreviewAction(RMBaction, true)
        end
    end
end
AddComponentPostInit("playercontroller", playercontroller)

AddAction("ITEM_PORTAL", "跃迁", function(act)
    if act == nil or act.doer == nil or act.pos == nil then
        return false
    end

    local inst = act.doer
 --[[  local inventory = inst.components.inventory
    if not inst.components.obrsword_power then
        return false
    end
    if inst.components.obrsword_power.current < TUNING.item_port_power or not inst:HasTag("obsword_mapgoto") then
        inst:DoTaskInTime(1.5, function()
            inst.components.talker:Say("彩虹之玉共鸣之时，四方才可穿越")
        end)
        return false
    end 
    ]]--
    local inventory = inst.components.inventory

    -- 检查玩家是否具有 `obrsword_power` 组件
    if not inst.components.obrsword_power then
        return false
    end

    -- 检查 `obrsword_power` 组件的当前值是否大于等于 60 或者玩家是否有 `obsword_mapgoto` 标签
    if inst.components.obrsword_power.current < 60 and not inst:HasTag("obsword_mapgoto") then
        inst:DoTaskInTime(0.1, function()
            inst.components.talker:Say("彩虹之玉共鸣之时，四方才可穿越")
        end)
        inst:DoTaskInTime(1.5, function()
            inst.components.talker:Say(string.format("需要 %d 能量才能跃迁，当前能量为 %d", 60,
                inst.components.obrsword_power.current))
        end)
        return false
    end

    local targetpos = act.pos:GetPosition()
    local pt = inst:GetPosition()
    local offset = FindWalkableOffset(pt, math.random() * 2 * PI, 3 + math.random(), 16, false, true, noentcheckfn,
        true, true) or
                       FindWalkableOffset(pt, math.random() * 2 * PI, 5 + math.random(), 16, false, true, noentcheckfn,
            true, true) or
                       FindWalkableOffset(pt, math.random() * 2 * PI, 7 + math.random(), 16, false, true, noentcheckfn,
            true, true)
    if offset ~= nil then
        pt = pt + offset
    end

    local portal = SpawnPrefab("pocketwatch_portal_entrance")
    if portal then
        if portal and portal.components.teleporter then
            portal.components.teleporter.onActivate = function(_, doer)
                if doer.components.talker ~= nil then
                    doer.components.talker:ShutUp()
                end

                if doer.components.sanity ~= nil and
                    not (doer:HasTag("pocketwatchcaster") or doer:HasTag("nowormholesanityloss")) then
                    doer.components.sanity:DoDelta(-(TUNING.item_port_sanity or 30))
                end
                if doer.components.hunger ~= nil then
                    doer.components.hunger:DoDelta(-(TUNING.item_port_hunger or 75))
                end
                if doer.components.obrsword_power then
                    doer.components.obrsword_power:DoDelta(-(TUNING.item_port_power or 45))
                end
            end
        end
        portal.Transform:SetPosition(pt:Get())
        portal:SpawnExit(TheShard:GetShardId(), targetpos.x or pt.x, 0, targetpos.z or pt.z)
        inst.SoundEmitter:PlaySound("wanda1/wanda/portal_entrance_pre")
        local hands = inst.components.inventory:GetEquippedItem(EQUIPSLOTS.HANDS)
        if hands and hands.components.finiteuses then
            hands.components.finiteuses:Use(TUNING.item_port_finiteuses)
        end
    end
    return true
end).map_action = true
ACTIONS.ITEM_PORTAL.do_not_locomote = true
ACTIONS.ITEM_PORTAL.rmb = true
STRINGS.ITEM_MODNAME = ACTIONS.ITEM_PORTAL.mod_name

AddStategraphActionHandler("wilson", ActionHandler(GLOBAL.ACTIONS.ITEM_PORTAL, nil))
AddStategraphActionHandler("wilson_client", ActionHandler(GLOBAL.ACTIONS.ITEM_PORTAL, nil))

local BLINK_MAP_MUST = {"CLASSIFIED", "globalmapicon", "fogrevealer"}
ACTIONS_MAP_REMAP[ACTIONS.ITEM_PORTAL.code] = function(act, targetpos)
    local doer = act.doer
    if doer == nil then
        return nil
    end
    if doer.item_portal then
        return nil
    end
    if not TheWorld.Map:IsVisualGroundAtPoint(targetpos.x, targetpos.y, targetpos.z) then
        local ents = TheSim:FindEntities(targetpos.x, targetpos.y, targetpos.z, PLAYER_REVEAL_RADIUS * 0.4,
            BLINK_MAP_MUST)
        local revealer
        local MAX_WALKABLE_PLATFORM_DIAMETERSQ = TUNING.MAX_WALKABLE_PLATFORM_RADIUS *
                                                     TUNING.MAX_WALKABLE_PLATFORM_RADIUS * 4 -- Diameter.
        for _, v in ipairs(ents) do
            if doer:GetDistanceSqToInst(v) > MAX_WALKABLE_PLATFORM_DIAMETERSQ then
                -- Ignore close boats because the range for aim assist is huge.
                revealer = v
                break
            end
        end
        if revealer == nil then
            return nil
        end
        targetpos.x, targetpos.y, targetpos.z = revealer.Transform:GetWorldPosition()
        if revealer._target ~= nil then
            -- Server only code.
            local boat = revealer._target:GetCurrentPlatform()
            if boat == nil then
                return nil
            end
            targetpos.x, targetpos.y, targetpos.z = boat.Transform:GetWorldPosition()
        end
    end
    local act_remap = BufferedAction(doer, nil, ACTIONS.ITEM_PORTAL, act.invobject, targetpos)
    return act_remap
end
